-------The Wizard of Id's WizMath------
A 4am crack                  2020-06-18
---------------------------------------

Name: The Wizard of Id's WizMath
Genre: educational
Year: 1984
Credits: Chuck Benton
Publisher: Sierra On-Line
Platform: Apple ][+ or later (48K)
Media: 5.25-inch disk
Sides: 1
OS: DOS 3.3
Previous cracks: none

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but crashes when starting
  a game

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Passport
  copies but applies no patches
  copy has same behavior has others

Copy ][+ nibble editor
  no obvious shenanigans
  no track $23

Disk Fixer
  standard DOS 3.3
  T11 is standard DOS 3.3 disk catalog
  T01,S09 -> startup program is "HELLO"

Why didn't COPYA work?
  Probably a runtime protection check
  looking for... something? Between the
  sectors I guess?

Why didn't Locksmith FDB work?
  ditto

Why didn't my EDD copy work?
  An excellent question... that I can't
  yet answer.

Next steps:

  1. Find the protection check
  2. Disable it
  3. Declare victory (*)

(*) but do not go to the gym until
    there's a vaccine

                   ~

               Chapter 1
           Unlucky In Cards


Since my copy goes down a different
code path than the original, I'm
guessing there is a runtime protection
check somewhere. One thing that all
protection checks have in common is
they need to turn on the drive motor by
accessing a specific address in the
$C0xx range. For slot 6, it's $C0E9,
but to allow disks to boot from any
slot, developers usually use code like
this:

  LDX <slot number x 16>
  LDA $C089,X

There's nothing that says where the
slot number has to be, although the
disk controller ROM routine uses zero
page $2B and lots of disks just reuse
that. There's also nothing that says
you have to use the X-register as the
index, or that you must use the
accumulator as the load register. But
most RWTS code does, out of convention
I suppose (or possibly fear of messing
up such low-level code in subtle ways).

Also, since developers don't actually
want people finding their protection-
related code, they may try to encrypt
it or obfuscate it to prevent people
from finding it. But eventually, the
code must exist and the code must run,
and it must run on my machine, and I
have the final say on what my machine
does or does not do.

But sometimes you get lucky.

Turning to my trusty Disk Fixer sector
editor, I search the non-working copy
for "BD 89 C0", which is the opcode
sequence for "LDA $C089,X".

[Disk Fixer]
  ["F"ind]
    ["H"ex]
      ["BD 89 C0"]

                 --v--

------------- DISK SEARCH -------------

$00/$07-$4F   $0B/$01-$73

                 --^--

The match on track 0 is part of DOS 3.3
and is legitimate. Let's see what lurks
on track $0B.

According to Copy ][+ track/sector map,
T0B,S01 is part of a file named
"OBJ.8800" which -- and I'm just making
an educated guess here -- is probably
loaded at $8800. Following the file and
counting sectors, it appears that the
sector in question ends up at $9600.
Thanks to the 4-byte offset in DOS 3.3
files, offset $73 will end up being
loaded at $966F.

]PR#6
...<Ctrl-C>...

No reset or keyboard trapping, so a
well-timed <Ctrl-C> gets me a working
BASIC prompt with DOS in memory.

]LIST

 10  HOME
 15  PRINT "MAXFILES 1"
 20  PRINT "BLOAD PICTURES"
 30  PRINT "BLOAD EMULATE.TBL"
 40  PRINT "BLOAD DRAW.OBJ"
 48  PRINT "BLOAD OBJ.8800,A$8800
     "
 49  PRINT "BLOAD OBJ.7000,A$7000
     "
 50  PRINT "BLOAD NAMES"
 60  PRINT "BRUN OBJ.0800"

]CATALOG

DISK VOLUME 254

 A 006 HELLO
 B 026 OBJ.0800
 B 026 OBJ.7000
 B 020 OBJ.8800
 B 013 EMULATE.TBL
 B 003 DRAW.OBJ
 B 093 PICTURES
 B 034 SPOOK
 B 009 SPKESC0
 B 009 SPKESC1
 B 034 KING
 B 006 NAMES

]MAXFILES 1
]BLOAD OBJ.8800,A$8800
]CALL -151

*966FL

966F-   BD 89 C0    LDA   $C089,X

Bingo.

                   ~

               Chapter 2
       Are You Watching Closely?


It appears the routine itself starts at
$9668. Immediately before that is a
JMP instruction, and there don't seem
to be any branches to $9668.

*9668L

9668-   A9 00       LDA   #$00
966A-   AA          TAX
966B-   A8          TAY

; seek to track 0
966C-   20 52 B0    JSR   $B052

; turn on drive motor
966F-   BD 89 C0    LDA   $C089,X

; maybe a Death Counter?
9672-   A9 05       LDA   #$05
9674-   8D 00 BB    STA   $BB00
9677-   20 AB 96    JSR   $96AB

*96ABL

96AB-   A9 1C       LDA   #$1C
96AD-   8D 02 BB    STA   $BB02

; reset data latch
96B0-   BD 8E C0    LDA   $C08E,X

; find $D5 nibble
96B3-   BD 8C C0    LDA   $C08C,X
96B6-   10 FB       BPL   $96B3
96B8-   C9 D5       CMP   #$D5
96BA-   EA          NOP
96BB-   EA          NOP
96BC-   F0 0F       BEQ   $96CD

; $BB01/$BB02 is a failsafe counter for
; finding the nibble. (Note that $BB01
; is uninitialized, but it's the low
; byte of the 2-byte word so it doesn't
; matter much.)
96BE-   CE 01 BB    DEC   $BB01
96C1-   D0 F0       BNE   $96B3
96C3-   CE 02 BB    DEC   $BB02
96C6-   D0 EB       BNE   $96B3

; if we can't even find $D5, pop the
; stack (because we JSR'd here) and
; jump to what I assume is The Badlands
96C8-   68          PLA
96C9-   68          PLA
96CA-   4C 9B 96    JMP   $969B

; execution continues here (from $96BC)
; once we find the $D5 nibble
96CD-   BD 8C C0    LDA   $C08C,X
96D0-   10 FB       BPL   $96CD

; now looking for $AA
96D2-   C9 AA       CMP   #$AA
96D4-   D0 E2       BNE   $96B8
96D6-   48          PHA
96D7-   68          PLA
96D8-   BD 8C C0    LDA   $C08C,X
96DB-   10 FB       BPL   $96D8

; then $96
96DD-   C9 96       CMP   #$96
96DF-   D0 F1       BNE   $96D2
96E1-   A0 05       LDY   #$05
96E3-   20 10 97    JSR   $9710

*9710L

; a compact little subroutine that will
; skip over a given number of nibbles
; (Y is set in the caller)
9710-   BD 8C C0    LDA   $C08C,X
9713-   10 FB       BPL   $9710

; just burning cycles so we don't
; accidentally read the same nibble
; twice
9715-   48          PHA
9716-   68          PLA
9717-   88          DEY
9718-   D0 F6       BNE   $9710

; note that the last nibble read is
; still in A when we return
971A-   60          RTS

Continuing from $96E6...

; the 5th nibble needs to be $AA (this
; will be part of the sector number in
; the address field)
96E6-   C9 AA       CMP   #$AA
96E8-   D0 C9       BNE   $96B3
96EA-   BD 8C C0    LDA   $C08C,X
96ED-   10 FB       BPL   $96EA

; this will be the other half of the
; sector number in the address field
; (it's 4-and-4 encoded, $AA $AA -> 0)
96EF-   C9 AA       CMP   #$AA
96F1-   D0 C0       BNE   $96B3
96F3-   48          PHA
96F4-   68          PLA

; unconditionally (without counting)
; look for the next $D5 nibble, which
; should be the start of the data
; prologue for sector 0
96F5-   BD 8C C0    LDA   $C08C,X
96F8-   10 FB       BPL   $96F5
96FA-   C9 D5       CMP   #$D5
96FC-   D0 F7       BNE   $96F5
96FE-   EA          NOP
96FF-   BD 8C C0    LDA   $C08C,X
9702-   10 FB       BPL   $96FF

; $AA nibble, also part of the data
; prologue
9704-   C9 AA       CMP   #$AA
9706-   D0 F2       BNE   $96FA
9708-   EA          NOP

; skip $100 nibbles
9709-   A0 00       LDY   #$00
970B-   20 10 97    JSR   $9710

; skip $5B more nibbles
970E-   A0 5B       LDY   #$5B

; note: this falls through to the same
; subroutine entry point we called
; earlier, which is nice
9710-   BD 8C C0    LDA   $C08C,X
9713-   10 FB       BPL   $9710
9715-   48          PHA
9716-   68          PLA
9717-   88          DEY
9718-   D0 F6       BNE   $9710
971A-   60          RTS

So far, we've seeked to track 0, found
sector 0, and skipped basically all of
it.

Continuing from $967A...

; this will always branch, because the
; last thing that happened was that Y
; was decremented to 0, which is still
; technically positive as far as the
; 6502 is concerned
967A-   10 01       BPL   $967D

; and this is all bogus
967C-   20 C8 C0    JSR   $C0C8
967F-   30 5D       BMI   $96DE
9681-   8C C0 90    STY   $90C0
9684-   F8          SED

Are you watching closely? We took an
(unconditional) branch into the middle
of an instruction, from $967A to $967D,
but the monitor is showing a listing of
$967C and beyond. None of that is real;
it will never be executed. Execution
continues at $967D.

*967DL

; burn some number of cycles
; (Y starts at 0 because that's where
; it ended in the previous subroutine)
967D-   C8          INY
967E-   C0 30       CPY   #$30

; maybe keeping a rolling checksum of
; raw data latch values?!?
9680-   5D 8C C0    EOR   $C08C,X

; EOR does not affect the carry bit,
; so this branches based on the CPY,
; not any of the data latch values
9683-   90 F8       BCC   $967D

; OK not a rolling checksum, because
; now we're discarding it and reading
; the data latch again
9685-   BD 8C C0    LDA   $C08C,X

; on first glance this looks like a
; regular LDA/BPL loop to read a nibble
; EXECPT it's branching forwards, not
; backwards
9688-   10 0A       BPL   $9694

; now comparing the RAW DATA LATCH READ
; at $9685
968A-   C9 C9       CMP   #$C9
968C-   D0 0D       BNE   $969B

; success path falls through here --
; turn off drive motor and return the
; Death Counter value to the caller
968E-   BD 88 C0    LDA   $C088,X
9691-   4C A7 96    JMP   $96A7
...
96A7-   AD 00 BB    LDA   $BB00
96AA-   60          RTS

; execution continues here (from $9688)
; if the raw data latch read is NOT the
; expected $C9 value
9694-   EA          NOP
9695-   EA          NOP

; apparently we get one more chance to
; find the right nibble, because after
; burning some cycles with the NOPs, if
; the data latch has gone high (meaning
; a full nibble value is available), we
; branch back to the CMP to see if it
; now matches the expected $C9 nibble
9696-   BD 8C C0    LDA   $C08C,X
9699-   30 EF       BMI   $968A

; decrement Death Counter and try again
969B-   CE 00 BB    DEC   $BB00
969E-   D0 D7       BNE   $9677

; out of chances -- corrupt the stack
; (two pushes but only one pull) and
; jump to what I previously called the
; "success" path, except now that the
; stack is corrupted, it will crash
; when it tries to return to the caller
96A0-   48          PHA
96A1-   98          TYA
96A2-   48          PHA
96A3-   68          PLA
96A4-   4C 8E 96    JMP   $968E

So that's it. At one of two exact times
after sector 0, there needs to be a $C9
nibble. If we find it, we gracefully
exit; otherwise, we corrupt the stack
and crash.

So why can't EDD copy it?

                   ~

               Chapter 3
     Now You See It, Now You Don't


Using the Copy ][+ nibble editor, we
can look at the raw nibbles on track 0,
including the crucial $C9 nibble. Here
is track 0, starting near the end of
the data field of sector 0. (I've
converted the inverse characters into
"+" signs to indicate where Copy ][+
found one or more timing bits after a
nibble.)

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
----------------------------------------

TRACK: 00  START: 2029  LENGTH: 188E

2188: 96 96 96 96 96 96 96 96   VIEW
2190: 96 96 96 96 96 96 96 EA
2198: EC 9A DE AA EB EA+FF+FF+
21A0: FF+FF+BA+FF+FF+FF+FF+FF+
21A8: FF+FF+FF+FE+C9+FF+FF+FF+ <-21AC
21B0: FF+FF+FF+AC+FF+99+FF+99+
21B8: E4 E4 E4+FF+A4 FF+9C FF+
21C0: FF+FF+FF+FF+92 92 92 FF+
21C8: 96 FF+FF+FF+9D F2+FF+FF+

                 --^--

The exact nibble offsets are not
important (they'll change if we re-read
the disk), but this is what we have at
offset $219A:

DE AA EB     ; data epilogue
EA
FF FF FF FF
BA
FF FF FF FF FF FF FF FF
FE
C9
...

Seems normal enough. But let's read the
same track again...

                 --v--

26F0: 96 96 96 96 96 96 96 96   VIEW
26F8: 96 96 96 96 96 EA EC 9A
2700: DE AA EB EA+D9+FF+E5 E5+
2708: FF+FF+FF+FF+FF+9F E7 F9
2710: FC C9 C9 C9+FF+FF+FF+D6  <-2712
2718: DB+FF+A4+FF+FF+FF+AD 94
2720: FF+FF+A9 A9 CB+FF+FF+FF+
2728: FF+FF+FF+EA FF+FF+FF+FF+
2730: FF+BA+FF+FF+FF+FF+FF+FF+

                 --^--

Again, the exact nibble offsets are not
important. But look at the "data" after
sector 0 (starting at offset $2700):

DE AA EB     ; data epilogue
EA
D9
FF
E5
E5
FF FF FF FF FF
9F
E7
F9
FC
C9
C9
C9

In fact, this region of the original
disk will look different every time we
read it. How? I'm glad you asked.

There's only one thing you can put on a
disk that will change every time you
read it: nothing. And by "nothing," I
mean "a long sequence of zero bits."
And that's what is on the original disk
before (and, as it turns out, after)
the $C9 nibble: nothing.

A bit of background. When we say a
"zero bit," we really mean "the lack of
a magnetic state change." The Disk II
drive isn't digital; it's analog. If it
doesn't see a state change in a certain
period of time, it calls that a "0". If
it does see a change, it calls that a
"1". But the drive can only tolerate a
lack of state changes for so long --
about as long as it takes for two bits
to go by.

Fun fact(*): this is why disks use
nibbles as an intermediate on-disk
format in the first place. No valid
nibble contains more than two zero bits
consecutively, when written from most-
significant to least-significant bit.

(*) not guaranteed, actual fun may vary

So what happens when a drive doesn't
see a state change after the equivalent
of two consecutive zero bits? The drive
thinks the disk is weak, and the MC3470
chip inside the drive starts increasing
the amplification to try to compensate,
looking for a valid signal.  But there
is no signal. There is no data. There
is just a yawning abyss of nothingness.
Eventually, the drive gets desperate
and amplifies so much that it starts
returning random bits based on ambient
noise from the disk motor and the
magnetism of the Earth.

Seriously.

It's trivial to write zero bits to a
disk; just write a #$00 nibble to write
8 zero bits -- like any other 8-bit
nibble. You can write whatever you want
to a disk; it doesn't need to be what
DOS would consider a "valid" nibble.
But when you read that nibble back, the
drive can't handle 8 zero bits in a
row, so it will actually return some
random bits. Which is why no one does
that.

Returning random bits doesn't sound
very useful for a storage device, but
it's exactly what the developer wanted,
and that's exactly what this copy
protection scheme depends on. Here's
why:

Bit copiers can't duplicate a long
sequence of zero bits.

Why? Because that's not what they see.
What they see is some random bits --
the real zero bits interspersed with
phantom "1" bits. So that's what they
write to the target disk. Whatever
randomness they get when they read the
original disk will essentially get
"frozen" onto the copy.

But wait, it gets worse.

                   ~

               Chapter 4
  In Which We Jump Out Of The System,
           Then Jump Back In


To understand exactly what is on the
original disk, and why this copy
protection works, we need to jump
outside the system and use modern tools
that can see the disk at a different
level.

With an Applesauce hardware controller,
a modified drive, and the associated
Applesauce.app software, we can get a
flux-level read of exactly what is on
the original disk.

Starting immediately after the data
epilogue of sector 0:

11101010     ; $EA nibble
00000000     ; long sequence of 0 bits
...          ; (99 "bits" long)
1111111100   ; $FF/10 (sync nibble)
1111111100   ; $FF/10 (sync nibble)
111111100    ; $FE/9 (sync nibble)
11001001     ; $C9 nibble
00000000     ; long sequence of 0 bits
...          ; (1141 "bits" long)

Those 99 bits after the $EA nibble can
randomly resolve into anything, which
will then be interpreted as nibbles.
The sync nibbles might resynchronize
the stream, so that when it reads the
data latch at the crucial moment, the
$C9 nibble is there. Or they might not.
There aren't enough sync nibbles to
guarantee resynchronization. (See
p. 3-8 of "Beneath Apple DOS" for a
more detailed explanation of why you
need at least four $FF/10 nibbles to
resynchronize in all cases.) With the
element of randomness, it's possible
that those 99 zero bits could resolve
to a bitstream where the nibbles are
still out of phase at the crucial
moment when it checks for the $C9
nibble. In that case, it will decrement
the Death Counter in $BB00 and start
over. It has 5 chances to get it right.

Could this actually happen? I have the
original disk and real hardware, so
let's find out. Since the protection
routine is self-initializing, self-
contained, unencrypted, and returns the
value of the Death Counter, I can non-
destructively test my original disk by
loading the routine and calling it
repeatedly, and looking at the return
value.

[S6,D1=original disk]

]PR#6
...<Ctrl-C>

]MAXFILES 1
]BLOAD OBJ.8800,A$8800
]CALL -151

*300L

0300-   20 68 96    JSR   $9668
0303-   AA          TAX
0304-   FE 40 03    INC   $0340,X
0307-   A9 AE       LDA   #$AE
0309-   20 F0 FD    JSR   $FDF0
030C-   CE 40 03    DEC   $0340
030F-   D0 EF       BNE   $0300
0311-   60          RTS

*340:0 0 0 0 0 0
*300G
...

*341.345

0341- 02 08 0C 29 C1

That's a nice distribution. About 90%
of the time, it succeeds on the first
or second try. But it clearly shows
that the Death Counter is a necessary
component of this copy protection. Two
times out of 256, it only succeeded on
the last try!

I ran the same test a few more times
and got a similar distribution. But on
the fourth run, it crashed, confirming
that -- about once every 1000 runs --
this protection check can and will fail
on an original disk.

                   ~

               Chapter 5
  May The Odds Be Ever In Your Favor


So what happens on a bit copy? The
short answer is... it depends. It might
work! It's random! Maybe it will
interpret the random bitstream as a
sequence of nibbles that is just the
right length AND just happens to
synchronize to the $C9 nibble, in which
case the copy will pass the protection
check.

The long answer is... the deck is
stacked against you.

In the general case, bit copiers are
trying to do the impossible, and they
succeed a surprising amount of the
time. On a disk like this, most parts
of each track are highly structured.
There are sectors. There are address
fields and data fields, each with
prologues and epilogues and internal
checksums. By design, you can verify
that you read each of these things
properly.

But the bits between the sectors are
the "wild west," with little to no
structure to fall back on. Compounding
their troubles, bit copiers can not
tell the difference between 1 and 2
zero bits after a nibble. The disk is
just too fast and the CPU is too slow;
there's barely enough time to determine
that there's a single zero bit (and
even that routine has limitations that
can be exploited).

Sync nibbles ($FF/10) are generally OK
because they can assume that those are
being used as sync nibbles. But this is
an assumption, not anything they can
verify in real time, and after 4 sync
nibbles in a row, even advanced bit
copiers like EDD will start writing out
$FF/9 instead, unless you tell them
otherwise. (Adventure International
uses this assumption against copiers by
having a long field of $FF/10 nibbles
and noticing that they get rewritten as
$FF/9 nibbles. See 4am crack no. 1563
"The Kingdom of Facts" for details.)

Anyway, bit copiers have a fundamental
problem: they can't tell exactly what's
on the disk, because the disk is too
fast and the CPU is too slow. So they
compensate, guess, and reconstruct.
They build the most probable bitstream
based on the nibble stream at any given
point on the track. But they do not,
and can not, "duplicate" the bitstream.
Every copy is a reconstruction.

When you read complete garbage that
changes every time it comes around,
then try to reconstruct that into some
sort of reasonable bitstream that makes
sense as a nibble stream, you are going
to fail because there aren't really any
nibbles. There is just the yawning
abyss.

Using the same Applesauce software, I
imaged my non-working copy and examined
the problematic region after track 0,
sector 0. Keep in mind that each copy
would look slightly different, due to
how it reads the random garbage. But
even a single copy is instructive.

111010100    ; $EA/9
110000000    ; $C0/9
101110010    ; $B9/9
100111000    ; $9C/9
110100100    ; $D2/9
100000100    ; $82/9
10100111     ; $A7
10100111     ; $A7
101001010    ; $A5/9
100000000    ; $80/9
101010000    ; $A8/9
10111111     ; $BF
11001111     ; $CF
11110011     ; $F3
11111001     ; $F9
10010010     ; $92
100001100    ; $86/9
111010010    ; $E9

This is EXTREMELY interesting. At first
glance, it looks nothing like the
original disk, because some of those 99
0 bits turned into 1 bits. Fine, we
knew that would happen. But more
importantly, there's no $C9 nibble.

Or is there?

If you take the $BF nibble and ignore
the first two bits, you get this:

  111111
11001111
11110011
11111001
10010010

Here, let me rewrite that for you:

1111111100   ; $FF/10
1111111100   ; $FF/10
111111100    ; $FE/9
110010010    ; $C9/9

So there is a $C9 nibble, but it's out
of phase, and the sync field isn't long
enough to resync the data latch to see
it. But more importantly, IT'S IN THE
WRONG PLACE. Count the bits. Between
the $EA nibble and the first $FF/10
nibble, the original disk has 99 bits.
This copy only has 91 bits. By the time
we try to read the crucial $C9 nibble,
we're long past it.

Finally, because the rest of the abyss
has been turned into "real" nibbles,
re-reading the track will not change
the result. The bitstream is too short,
the crucial nibble is out of phase,
there's no longer any meaningful
randomness, and you're all out of luck.

The original disk might fail 0.1% of
the time, but this copy will fail 100%
of the time.

                   ~

               Chapter 6
           Back To The Crack


Searching the disk for the bytes "20 68
96" (JSR $9668, the entry point of the
protection routine), I find one match
on track $17.

; change "JSR" to "BIT"
T17,S08,$D1: 20 -> 2C

The code immediately following does not
even check the return value (the Death
Counter), and there do not appear to be
any other side effects or delayed anti-
tamper checks.

                   ~

               Epilogue


Out of curiosity, I found a scan of an
old EDD program list, which gives brief
descriptions of programs that require
non-default parameters. This game was
not listed, but Sierra reused the same
protection on other disks, including
"King's Quest." Here is what they
suggest to make a protected backup:

                 --v--

       drive speed critical
       recopy t0 until boots
       t0 parm normal or bitcopy
         and/or autonc or manualnc

                 --^--

"recopy t0 until boots"

Now you know why.

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 2203
------------------EOF------------------
